/**
 * 
 */
package org.youisoft.summer.evaluator.function.interpolation;

import java.util.List;

import org.youisoft.summer.evaluator.InvokeException;
import org.youisoft.summer.evaluator.context.CalContext;
import org.youisoft.summer.evaluator.function.AbstractFunction;
import org.youisoft.summer.evaluator.operator.Operator;
import org.youisoft.summer.evaluator.operator.base.MapOperator;
import org.youisoft.summer.evaluator.operator.base.NumberOperator;

/**
 * @author zijiang.jl
 *
 */
public class NewtonFunction extends AbstractFunction{
	@Override
	public String getName() {
		return "newton";
	}
	
	

	@Override
	protected boolean checkOperator(int index, Operator<?> operator) {
		if(index==0&&NumberOperator.class.isAssignableFrom(operator.getClass())) return true;
		if(index==1&&MapOperator.class.isAssignableFrom(operator.getClass())) return true;
		return false;
	}


	@Override
	protected int minOperatorCount() {
		return 2;
	}
	

	@Override
	public <C, R, P> Operator<?> doInvoke(CalContext<C, R, P> context,
			Operator<?>... operator) throws InvokeException {
		NumberOperator x=NumberOperator.class.cast(operator[0]);
		
		if(operator[1] instanceof MapOperator<?>) {
			MapOperator<?> map=MapOperator.class.cast(operator[1]);
			java.util.List<Point> list=new java.util.ArrayList<Point>();
			Point p=null;
			for(java.util.Iterator<?> it=map.value().keySet().iterator();it.hasNext();){
				Object key=it.next();
				Object value=map.value().get(key);
				if(key instanceof NumberOperator&&value instanceof NumberOperator){
					p=new Point();
					p.setX(NumberOperator.class.cast(key).value());
					p.setY(NumberOperator.class.cast(value).value());
					list.add(p);
				}

			}
			if(list.size()==0) return Operator.nullInstance;
			return x.setValue(this.newton(x.value(),list));
		}
		return Operator.nullInstance;
	}
	
	private Number newton(Number x,java.util.List<Point> list){

		if(list.size()==1) return list.get(0).getY();
		
		Number result=this.difference(list);
		
		list.remove((list.size()-1));
		
		result=result.doubleValue()*this.xPart(x, list).doubleValue();
		
		
		result=newton(x,list).doubleValue()+result.doubleValue();
		
		return result;
	}
	
	
	private Number xPart(Number x,List<Point> l){
		double result=1;
		for(Point p:l){
			result*=(x.doubleValue()-p.getX().doubleValue());
		}
		return result;
	}
	
	
	private Number difference(List<Point> l){
		if(l.size()==2) return this.difference(l.get(0), l.get(1));
		java.util.List<Point> temp0=new java.util.ArrayList<Point>();
		java.util.List<Point> temp1=new java.util.ArrayList<Point>();
		for(int i=0;i<l.size()-2;i++){
			temp0.add(l.get(i));
			temp1.add(l.get(i));
		}
		
		Point p0=l.get(l.size()-1);
		temp0.add(p0);
		
		Point p1=l.get(l.size()-2);
		temp1.add(p1);

		return (this.difference(temp0).doubleValue()-this.difference(temp1).doubleValue())/(p0.getX().doubleValue()-p1.getX().doubleValue());	
	}
	
	private Number difference(Point p0,Point p1){
		return (p1.getY().doubleValue()-p0.getY().doubleValue())/(p1.getX().doubleValue()-p0.getX().doubleValue());
	}
	
	private class Point{
		private Number x;
		private Number y;
		
		public Point(){
			
		}
		
		public Point(Number x,Number y){
			this.x=x;
			this.y=y;
		}
		
		public final Number getX() {
			return x;
		}
		public final void setX(Number x) {
			this.x = x;
		}
		public final Number getY() {
			return y;
		}
		public final void setY(Number y) {
			this.y = y;
		}	
	}

	
	public static void main(String[] args) {
		NewtonFunction nf=new NewtonFunction();
		java.util.List<Point> list=new java.util.ArrayList<Point>();
		list.add(nf.new Point(1,0));
		list.add(nf.new Point(3,2));
		list.add(nf.new Point(4,15));
		list.add(nf.new Point(7,12));
		
		Number num=nf.difference(list);
		System.out.println(num);
		
	}

}
